package org.diretto.web.richwebclient.view.widgets.upload.client;
import java.util.HashMap;
import java.util.Map;
import org.diretto.web.richwebclient.view.widgets.upload.client.base.FileInfo;
import org.diretto.web.richwebclient.view.widgets.upload.server.MultipleUpload;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.ui.VButton;
import com.vaadin.terminal.gwt.client.ui.dd.VHtml5File;
/**
* The client side component of the {@link MultipleUpload}.
*
* @author Tobias Schlecht
*/
public final class VMultipleUpload extends Composite implements Paintable
{
private static final int POLLING_INTERVAL = 3000;
private HorizontalPanel wrapperPanel = null;
private VButton button = null;
private FileUpload fileUpload = null;
private String buttonCaption = null;
private Timer timer = null;
private String paintableID;
private ApplicationConnection applicationConnection;
private boolean initialized = false;
private final Map<String, VHtml5File> files = new HashMap<String, VHtml5File>();
/**
* Constructs a {@link VMultipleUpload}.
*/
public VMultipleUpload()
{
wrapperPanel = new HorizontalPanel();
initWidget(wrapperPanel);
}
/**
* Initializes this {@link Widget}.
*
* @param buttonStyleName The style of the upload button
*/
private void initUpload(String buttonStyleName)
{
timer = new Timer()
{
@Override
public void run()
{
applicationConnection.sendPendingVariableChanges();
}
};
fileUpload = new FileUpload();
fileUpload.addStyleName("outside-screen");
button = new VButton();
if(buttonStyleName != null && !buttonStyleName.equals(""))
{
button.addStyleName(button.getStylePrimaryName() + "-" + buttonStyleName);
button.addStyleName(buttonStyleName);
}
button.addClickHandler(new ClickHandler()
{
public void onClick(ClickEvent event)
{
fireNativeClick(fileUpload.getElement());
}
});
FormPanel formPanel = new FormPanel();
formPanel.add(fileUpload);
wrapperPanel.add(button);
wrapperPanel.add(formPanel);
}
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection applicationConnection)
{
if(applicationConnection.updateComponent(this, uidl, true))
{
return;
}
paintableID = uidl.getId();
this.applicationConnection = applicationConnection;
if(!initialized)
{
initUpload(uidl.getStringAttribute("buttonStyleName"));
initialized = true;
}
if(!uidl.getStringVariable("buttonCaption").equals(buttonCaption))
{
buttonCaption = uidl.getStringVariable("buttonCaption");
button.setText(buttonCaption);
}
if(initialized)
{
String[] uploadFiles = uidl.getStringArrayVariable("uploadFiles");
for(String fileName : uploadFiles)
{
if(files.containsKey(fileName))
{
post(uidl.getStringVariable("target_" + fileName.hashCode()), fileName);
}
}
String[] cancelFiles = uidl.getStringArrayVariable("cancelFiles");
for(String fileName : cancelFiles)
{
files.remove(fileName);
}
applicationConnection.updateVariable(paintableID, "cancelFilesConfirmed", cancelFiles, false);
if(uidl.getBooleanVariable("stopPolling"))
{
timer.cancel();
button.setEnabled(true);
applicationConnection.updateVariable(paintableID, "stopPolling", false, true);
}
}
}
/**
* Executes the HTTP POST operation for the given data.
*
* @param targetURL The target {@code URL}
* @param fileName The name of the file
*/
private void post(String targetURL, String fileName)
{
final VHtml5File file = files.remove(fileName);
XMLHttpRequest xmlHttpRequest = (XMLHttpRequest) XMLHttpRequest.create();
xmlHttpRequest.open("POST", applicationConnection.translateVaadinUri(targetURL));
xmlHttpRequest.post(file);
}
/**
* This <i>member</i> class extends the
* {@link com.google.gwt.user.client.ui.FileUpload} class, allows to select
* multiple files and handles the upcoming browser events.
*/
private final class FileUpload extends com.google.gwt.user.client.ui.FileUpload
{
/**
* Creates a {@link FileUpload}.
*/
private FileUpload()
{
super();
getElement().setPropertyString("multiple", "multiple");
sinkEvents(Event.ONCHANGE);
}
@Override
public void onBrowserEvent(Event event)
{
super.onBrowserEvent(event);
button.setEnabled(false);
int fileNumber = getNativeFileNumber(fileUpload.getElement());
String[] fileInfoStrings = new String[fileNumber];
for(int i = 0; i < fileNumber; i++)
{
VHtml5File file = getNativeFile(fileUpload.getElement(), i);
files.put(file.getName(), file);
FileInfo fileInfo = new FileInfo(file.getName(), file.getType(), file.getSize());
fileInfoStrings[i] = fileInfo.toString();
}
timer.scheduleRepeating(POLLING_INTERVAL);
applicationConnection.updateVariable(paintableID, "fileInfos", fileInfoStrings, true);
}
}
/**
* This <i>static member</i> class extends the
* {@link com.google.gwt.xhr.client.XMLHttpRequest} class and is responsible
* for the specific <i>native</i> parts of the HTTP POST operation.
*/
private static final class XMLHttpRequest extends com.google.gwt.xhr.client.XMLHttpRequest
{
/**
* Creates a {@link XMLHttpRequest}.
*/
protected XMLHttpRequest()
{
super();
}
/**
* Executes the specific <i>native</i> parts of the HTTP POST operation
* for the given file.
*
* @param file The file to post
*/
public native void post(VHtml5File file)
/*-{
this.setRequestHeader('Accept', 'text/html');
this.setRequestHeader('Content-Type', 'multipart/form-data');
this.send(file);
}-*/;
}
/**
* Triggers a click event at the given {@code DOM} {@link Element}.
*
* @param element The corresponding {@code DOM} {@code Element}
*/
private static native void fireNativeClick(Element element)
/*-{
element.click();
}-*/;
/**
* Returns the number of files of the given {@code DOM} {@link Element}.
*
* @param element The corresponding {@code DOM} {@code Element}
* @return The number of files
*/
private static native int getNativeFileNumber(Element element)
/*-{
return element.files.length;
}-*/;
/**
* Returns the file with the given index from the given {@code DOM}
* {@link Element}.
*
* @param element The corresponding {@code DOM} {@code Element}
* @param index The index of the file to be returned
* @return The requested {@code VHtml5File}
*/
private static native VHtml5File getNativeFile(Element element, int index)
/*-{
return element.files[index];
}-*/;
}